﻿Eg.Game = Eg.Class.extend({
  clearColor: '#000000',
  gravity: 0,
  screen: { x: 0, y: 0 },

  entities: [],

  namedEntities: {},
  collisionMap: Eg.CollisionMap.staticNoCollision,
  backgroundMaps: [],
  backgroundAnims: {},

  cellSize: 64,

  _deferredKill: [],
  _levelToLoad: null,
  _doSortEntities: false,

  loadLevel: function (data) {
    this.screen = { x: 0, y: 0 };

    // Entities
    this.entities = [];
    this.namedEntities = {};
    for (var i = 0; i < data.entities.length; i++) {
      var ent = data.entities[i];
      if (ent.type == "EntityPlayer" || ent.type == 'EntityProjectile'
      || ent.type == 'EntityDebris' || ent.type == 'EntityDelay' || ent.type == 'EntityBall' 
      || ent.type == 'EntityTrigger' || ent.type == 'EntityEarthquake' || ent.type == 'EntityBlob')
      this.spawnEntity(ent.name || '', ent.type, ent.x, ent.y, ent.settings);
    }
    this.sortEntities();

    // Map Layer
    this.collisionMap = Eg.CollisionMap.staticNoCollision;
    this.backgroundMaps = [];
    for (var i = 0; i < data.layer.length; i++) {
      var ld = data.layer[i];
      if (ld.name == 'collision') {
        this.collisionMap = new Eg.CollisionMap(ld.tilesize, ld.data);
      }
      else {
        var newMap = new Eg.BackgroundMap(ld.tilesize, ld.data, ld.tilesetName);
        newMap.anims = this.backgroundAnims[ld.tilesetName] || {};
        newMap.repeat = ld.repeat;
        newMap.distance = ld.distance;
        newMap.foreground = !!ld.foreground;
        this.backgroundMaps.push(newMap);
      }
    }
  },

  getEntityByName: function (name) {
    return this.namedEntities[name];
  },


  getEntitiesByType: function (type) {
    var entityClass = typeof (type) === 'string' ? window[type] : type;

    var a = [];
    for (var i = 0; i < this.entities.length; i++) {
      var ent = this.entities[i];
      if (ent instanceof entityClass && !ent._killed) {
        a.push(ent);
      }
    }
    return a;
  },


  spawnEntity: function (name, type, x, y, settings) {
    var entityClass = typeof (type) === 'string' ? window[type] : type;

    if (!entityClass) {
      throw ("Can't spawn entity of type " + type);
    }
    var ent = new (entityClass)(x, y, settings || {});
    this.entities.push(ent);
    if (ent.name) {
      this.namedEntities[ent.name] = ent;
    }
    return ent;
  },


  sortEntities: function () {
    this.entities.sort(function (a, b) { return a.zIndex - b.zIndex; });
  },


  sortEntitiesDeferred: function () {
    this._doSortEntities = true;
  },


  removeEntity: function (ent) {
    // Remove this entity from the named entities
    if (ent.name) {
      delete this.namedEntities[ent.name];
    }

    // We can not remove the entity from the entities[] array in the midst
    // of an update cycle, so remember all killed entities and remove
    // them later.
    // Also make sure this entity doesn't collide anymore and won't get
    // updated or checked
    ent._killed = true;
    ent.checkAgainst = Eg.Entity.TYPE.NONE;
    ent.collides = Eg.Entity.COLLIDES.NEVER;
    this._deferredKill.push(ent);
  },

  run: function () {
    this.update();
    this.draw();
  },

  update: function () {
    // load new level?
    if (this._levelToLoad) {
      this.loadLevel(this._levelToLoad);
      this._levelToLoad = null;
    }

    // sort entities?
    if (this._doSortEntities) {
      this.sortEntities();
      this._doSortEntities = false;
    }

    // update entities
    for (var i = 0; i < this.entities.length; i++) {
      var ent = this.entities[i];
      if (!ent._killed) {
        ent.update();
      }
    }
    this.checkEntities();

    // remove all killed entities
    for (var i = 0; i < this._deferredKill.length; i++) {
      this.entities.erase(this._deferredKill[i]);
    }
    this._deferredKill = [];

    // update background animations
    for (var tileset in this.backgroundAnims) {
      var anims = this.backgroundAnims[tileset];
      for (var a in anims) {
        anims[a].update();
      }
    }
  },
  draw: function () {
    if (this.clearColor) {
      Eg.sys.clear(this.clearColor);
    }

    var mapIndex;
    for (mapIndex = 0; mapIndex < this.backgroundMaps.length; mapIndex++) {
      var map = this.backgroundMaps[mapIndex];
      if (map.foreground) {
        // All foreground layers are drawn after the entities
        break;
      }
      map.setScreenPos(this.screen.x, this.screen.y);
      map.draw();
    }

    for (var i = 0; i < this.entities.length; i++) {
      this.entities[i].draw();
    }

    for (mapIndex; mapIndex < this.backgroundMaps.length; mapIndex++) {
      var map = this.backgroundMaps[mapIndex];
      map.setScreenPos(this.screen.x, this.screen.y);
      map.draw();
    }
  },

  checkEntities: function () {
    // Insert all entities into a spatial hash and check them against any
    // other entity that already resides in the same cell. Entities that are
    // bigger than a single cell, are inserted into each one they intersect
    // with.

    // A list of entities, which the current one was already checked with,
    // is maintained for each entity.

    var hash = {};
    for (var e = 0; e < this.entities.length; e++) {
      var entity = this.entities[e];

      // Skip entities that don't check, don't get checked and don't collide
      if (
				e.type == Eg.Entity.TYPE.NONE &&
				e.checkAgainst == Eg.Entity.TYPE.NONE &&
				e.collides == Eg.Entity.COLLIDES.NEVER
			) {
        continue;
      }

      var checked = {},
				xmin = Math.floor(entity.pos.x / this.cellSize),
				ymin = Math.floor(entity.pos.y / this.cellSize),
				xmax = Math.floor((entity.pos.x + entity.size.x) / this.cellSize) + 1,
				ymax = Math.floor((entity.pos.y + entity.size.y) / this.cellSize) + 1;

      for (var x = xmin; x < xmax; x++) {
        for (var y = ymin; y < ymax; y++) {

          // Current cell is empty - create it and insert!
          if (!hash[x]) {
            hash[x] = {};
            hash[x][y] = [entity];
          }
          else if (!hash[x][y]) {
            hash[x][y] = [entity];
          }

          // Check against each entity in this cell, then insert
          else {
            var cell = hash[x][y];
            for (var c = 0; c < cell.length; c++) {

              // Intersects and wasn't already checkd?
              if (entity.touches(cell[c]) && !checked[cell[c].id]) {
                checked[cell[c].id] = true;
                Eg.Entity.checkPair(entity, cell[c]);
              }
            }
            cell.push(entity);
          }
        } // end for y size
      } // end for x size
    } // end for entities
  }
});